﻿#region <!-- Using Directives -->

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.XPath;
using Microsoft.VisualStudio.TestTools.UnitTesting;

#endregion

namespace WordSharpDev.Extensions
{
	internal static class XmlExtensions
	{
		#region <!-- Compare -->

		public static Boolean Compare(this XmlNode p_This, XmlNode p_Target)
		{
			if (p_This == null) return p_Target == null;
			if (p_Target == null) return p_This == null;

			return p_This.OuterXml == p_Target.OuterXml;
		}

		#endregion

		#region <!-- FormatXml -->

		public static String FormatXml(this String p_This)
		{
			if (String.IsNullOrEmpty(p_This)) return p_This;

			var l_Document = new XmlDocument();

			var l_Result = p_This;

			try
			{
				l_Document.LoadXml(l_Result);

				var l_StringBuilder = new StringBuilder();

				var l_Settings = new XmlWriterSettings { Indent = true, IndentChars = "  ", NewLineChars = "\r\n", NewLineHandling = NewLineHandling.Replace, OmitXmlDeclaration = true };

				using (var l_Writer = XmlWriter.Create(l_StringBuilder, l_Settings))
				{
					l_Document.Save(l_Writer);
				}

				l_Result = l_StringBuilder.ToString();
			}
			catch
			{
			}

			return l_Result;
		}

		#endregion

		#region <!-- FormatXmlData -->

		public static String FormatXmlData(this String p_This)
		{
			var l_Result = "";

			foreach (var i_Line in p_This.Split('\r', '\n'))
			{
				var l_Line = i_Line;

				while (l_Line.Contains("<!--")) l_Line = l_Line.Replace("<!--", "");

				while (l_Line.Contains("-->")) l_Line = l_Line.Replace("-->", "");

				while (l_Line.Contains("--")) l_Line = l_Line.Replace("--", "");

				l_Line = l_Line.Replace("<![CDATA[", "");
				l_Line = l_Line.Replace("]]>", "");

				l_Result += String.Format("<!--<![CDATA[{0}]]>-->", l_Line);
			}

			return l_Result;
		}

		#endregion

		#region <!-- IsValidXml -->

		public static Boolean IsValidXml(this String p_This)
		{
			if (p_This == null) return false;

			var l_Document = new XmlDocument();

			try
			{
				l_Document.LoadXml(p_This);

				return true;
			}
			catch
			{
				return false;
			}
		}

		#endregion

		#region <!-- AsXmlNode -->

		public static XmlNode AsXmlNode(this String p_This)
		{
			if (p_This == null) return null;

			var l_Document = new XmlDocument();

			try
			{
				l_Document.LoadXml(p_This);

				return l_Document.DocumentElement;
			}
			catch
			{
				return null;
			}
		}

		#endregion

		#region <!-- GetXmlNode -->

		public static XmlNode GetXmlNode(this String p_This, String p_XPath)
		{
			if (p_This == null) return null;
			if (p_XPath == null) return null;

			var l_Document = new XmlDocument();

			try
			{
				l_Document.LoadXml(p_This);

				return GetXmlNode(l_Document, p_XPath);
			}
			catch
			{
				return null;
			}
		}

		public static XmlNode GetXmlNode(this XmlDocument p_This, String p_XPath)
		{
			if (p_XPath == null) return null;
			if (p_This == null) return null;

			return GetXmlNode(p_This.DocumentElement, p_XPath);
		}

		public static XmlNode GetXmlNode(this XmlNode p_This, String p_XPath)
		{
			if (p_XPath == null) return null;
			if (p_This == null) return null;

			return p_This.SelectSingleNode(p_XPath);
		}

		#endregion

		#region <!-- GetXmlString -->

		public static String GetXmlString(this String p_This, String p_XPath)
		{
			if (p_This == null) return null;
			if (p_XPath == null) return null;

			var l_Document = new XmlDocument();

			try
			{
				l_Document.LoadXml(p_This);

				return GetXmlString(l_Document, p_XPath);
			}
			catch
			{
				return null;
			}
		}

		public static String GetXmlString(this XmlDocument p_This, String p_XPath)
		{
			if (p_XPath == null) return null;
			if (p_This == null) return null;

			return GetXmlString(p_This.DocumentElement, p_XPath);
		}

		public static String GetXmlString(this XmlNode p_This, String p_XPath)
		{
			if (p_XPath == null) return null;
			if (p_This == null) return null;

			var l_Node = p_This.SelectSingleNode(p_XPath);

			if (l_Node == null) return null;

			if (1 < l_Node.ChildNodes.Count) return null;

			return l_Node.InnerText;
		}

		#endregion

		#region <!-- GetNewChild -->

		private static XmlNode NewChild(this XmlNode p_This, String p_Name)
		{
			if (p_This.NamespaceURI.IsVoid())
			{
				return p_This.OwnerDocument.CreateElement(p_Name);
			}

			else
			{
				return p_This.OwnerDocument.CreateElement(p_Name, p_This.NamespaceURI);
			}
		}

		public static XmlNode GetNewChild(this XmlNode p_This, String p_Name)
		{
			if (p_This == null) return null;

			var l_Child = NewChild(p_This, p_Name);

			return p_This.AppendChild(l_Child);
		}

		public static XmlNode GetNewChildAfter(this XmlNode p_This, String p_After, String p_Name)
		{
			if (p_This == null) return null;

			var l_Ref = p_This.SelectSingleNode(p_After);

			var l_Child = NewChild(p_This, p_Name);

			if (l_Ref != null)
				return p_This.InsertAfter(l_Child, l_Ref);

			else
				return p_This.AppendChild(l_Child);
		}

		public static XmlNode GetNewChildBefore(this XmlNode p_This, String p_Before, String p_Name)
		{
			if (p_This == null) return null;

			var l_Ref = p_This.SelectSingleNode(p_Before);

			var l_Child = NewChild(p_This, p_Name);

			if (l_Ref != null)
				return p_This.InsertBefore(l_Child, l_Ref);

			else
				return p_This.AppendChild(l_Child);
		}

		#endregion

		#region <!-- SetNewParent -->

		public static XmlNode SetNewParent(this XmlNode p_This, XmlNode p_Parent)
		{
			if (p_This == null) return null;

			var l_Clone = p_This.CloneNode(true);

			p_This.ParentNode.RemoveChild(p_This);

			p_Parent.AppendChild(l_Clone);

			return l_Clone;
		}

		#endregion

		#region <!-- SetAttribute -->

		public static XmlNode SetAttribute(this XmlNode p_This, String p_Name, String p_Value)
		{
			if (p_This == null) return null;

			var l_Attribute = p_This.OwnerDocument.CreateAttribute(p_Name);

			l_Attribute.Value = p_Value;

			p_This.Attributes.Append(l_Attribute);

			return p_This;
		}

		#endregion

		#region <!-- RenameXml -->

		public static XmlNode RenameXml(this XmlNode p_This, String p_NewName)
		{
			if (p_This == null) return null;

			var l_NewNode = p_This.OwnerDocument.CreateNode(p_This.NodeType, p_NewName, p_This.NamespaceURI);

			foreach (XmlAttribute i_Attribute in p_This.Attributes)
			{
				l_NewNode.Attributes.Append((XmlAttribute)i_Attribute.Clone());
			}

			foreach (XmlNode i_Node in p_This.ChildNodes)
			{
				l_NewNode.AppendChild(i_Node.Clone());
			}

			return l_NewNode;
		}

		#endregion

		#region <!-- AppendData -->

		public static void AppendData(this XmlNode p_This, String p_Data)
		{
			if (p_This == null) return;
			if (p_Data == null) return;

			foreach (var i_Line in p_Data.Split('\r', '\n'))
			{
				var l_Line = i_Line;

				while (l_Line.Contains("<!--")) l_Line = l_Line.Replace("<!--", "");

				while (l_Line.Contains("-->")) l_Line = l_Line.Replace("-->", "");

				while (l_Line.Contains("--")) l_Line = l_Line.Replace("--", "");

				l_Line = l_Line.Replace("<![CDATA[", "");
				l_Line = l_Line.Replace("]]>", "");

				if (l_Line.IsVoid()) continue;

				p_This.AppendChild(p_This.OwnerDocument.CreateComment(String.Format("<![CDATA[{0}]]>", l_Line)));
			}

		}

		#endregion

		#region <!-- XPath -->

		public static String FindXPathTo(this XmlNode p_This, XmlNode p_Target, Boolean p_Exact = false)
		{
			return FromXPathToOther(p_This.CreateNavigator(), p_Target.CreateNavigator(), p_Exact);
		}

		public static String FindXPathFrom(this XmlNode p_This, XmlNode p_Source, Boolean p_Exact = false)
		{
			return FromXPathToOther(p_Source.CreateNavigator(), p_This.CreateNavigator(), p_Exact);
		}

		private static String FromXPathToOther(XPathNavigator p_FromXPath, XPathNavigator p_ToXPath, Boolean p_Exact = false)
		{
			if (p_ToXPath.ComparePosition(p_FromXPath) == XmlNodeOrder.Unknown) return null;

			if (p_ToXPath.ComparePosition(p_FromXPath) == XmlNodeOrder.Same) return ".";

			var l_AncestorPath = p_FromXPath.Clone();

			var l_RelativePathLeft = "";
			var l_SeparatorLeft = "";

			while (true)
			{
				if (!l_AncestorPath.MoveToParent()) break;

				if (l_AncestorPath.NodeType == XPathNodeType.Root) break;

				if (p_ToXPath.IsDescendant(l_AncestorPath)) break;

				l_RelativePathLeft = l_RelativePathLeft + l_SeparatorLeft + "..";

				l_SeparatorLeft = "/";
			}

			var l_CurrentPath = p_ToXPath.Clone();
			var l_ParentPath = p_ToXPath.Clone();

			var l_RelativePathRight = "";
			var l_SeparatorRight = "";

			while (true)
			{
				if (!l_ParentPath.MoveToParent()) break;

				if (l_ParentPath.NodeType == XPathNodeType.Root) break;

				if (l_CurrentPath.ComparePosition(l_AncestorPath) == XmlNodeOrder.Same) break;

				l_RelativePathRight = GetXPathFromParent(l_CurrentPath, p_Exact) + l_SeparatorRight + l_RelativePathRight;

				l_SeparatorRight = "/";

				if (!l_CurrentPath.MoveToParent()) break;
			}

			if (l_RelativePathLeft.IsVoid()) return l_RelativePathRight;
			if (l_RelativePathRight.IsVoid()) return l_RelativePathLeft;

			return String.Format("{0}/{1}", l_RelativePathLeft, l_RelativePathRight);
		}

		private static String GetXPathFromParent(XPathNavigator p_CurrentPath, Boolean p_Exact = false)
		{
			var l_ParentPath = p_CurrentPath.Clone();

			if (!l_ParentPath.MoveToParent()) return "/";

			if (p_CurrentPath.NodeType == XPathNodeType.Attribute)
			{
				return String.Format("@{0}", p_CurrentPath.LocalName);
			}
			else if (p_CurrentPath.NodeType == XPathNodeType.Element)
			{
				var l_Index = 1;

				foreach (XPathNavigator l_ChildPath in l_ParentPath.SelectChildren(XPathNodeType.Element))
				{
					if (l_ChildPath.ComparePosition(p_CurrentPath) == XmlNodeOrder.Same) break;

					if (l_ChildPath.LocalName == p_CurrentPath.LocalName) l_Index++;
				}

				if (p_Exact)
					return String.Format("{0}[{1}]", p_CurrentPath.LocalName, l_Index);

				else
					return String.Format("{0}", p_CurrentPath.LocalName);	
			}
			else
			{
				throw new NotSupportedException();
			}
		}

		#endregion
	}
}